Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-102895 Add an option local_exit in code.interact to block exit() from terminating the whole process #102896

Merged
merged 14 commits into from
Oct 18, 2023

Conversation

gaogaotiantian
Copy link
Member

@gaogaotiantian gaogaotiantian commented Mar 22, 2023

@arhadthedev arhadthedev added the stdlib Python modules in the Lib dir label Mar 22, 2023
@gaogaotiantian gaogaotiantian changed the title gh-102895 Replace exit and quit in code.interact for a reasonable and unified behavior gh-102895 Add an option block_exit in code.interact to block exit() from terminating the whole process Apr 7, 2023
@arhadthedev
Copy link
Member

@gvanrossum, @brettcannon (as the ones who committed to Lib/code.py the most), @birkenfeld (as the one who committed to Lib/pdb.py the most)

@gvanrossum
Copy link
Member

Looks like a fine idea. I won’t be able to review. Make sure to add tests.

@gaogaotiantian
Copy link
Member Author

Test is already added to make sure exit() has the same effect as EOFError()(Ctrl + D) when block_exit=True. I think in the original issue @ambv had some thoughts on the matter and @iritkatriel is normally the person reviewing code changes to pdb.

@brettcannon
Copy link
Member

as the ones who committed to Lib/code.py the most

Me, really?!? I can't even remember when I last touched the code, so I don't feel up for doing a code review.

@gaogaotiantian
Copy link
Member Author

gaogaotiantian commented Apr 16, 2023

Hi, I know this file has not been actively maintained for a while, but the code change is pretty straightforward and it's backward compatible. It will be at least beneficial for pdb's interact command. Maybe someone could take some time to review it? I can try to explain the logic if that helps. Thanks!

@gaogaotiantian
Copy link
Member Author

@brandtbucher if you could take a look at this when you have some time :)

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a full review, sorry.

Comment on lines 37 to 46
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local* is
provided, it is passed to the :class:`InteractiveConsole` constructor for
use as the default namespace for the interpreter loop. The :meth:`interact`
method of the instance is then run with *banner* and *exitmsg* passed as the
banner and exit message to use, if provided. The console object is discarded
after use.
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local*
or *block_exit* is provided, it is passed to the :class:`InteractiveConsole`
constructor for use as the default namespace for the interpreter loop.
The :meth:`interact` method of the instance is then run with *banner* and
*exitmsg* passed as the banner and exit message to use, if provided.
The console object is discarded after use.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't normally reflow the paragraph, it distracts the review. Just let the lines be uneven in length. :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I just thought the paragraph would be kind of ugly. Do you want me to convert this back to the uneven format? So only one line is modified?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please! :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be done.

@@ -23,27 +23,33 @@ build applications which provide an interactive interpreter prompt.
``'__doc__'`` set to ``None``.


.. class:: InteractiveConsole(locals=None, filename="<console>")
.. class:: InteractiveConsole(locals=None, filename="<console>", block_exit=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you come up with a better name? To me, allow_exit=True or even just exit=True feels better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the difference between the option on and off is whether exit() would terminate the whole process, or just the interactive console. By block_exit I was trying to describe the option "blocks" the exit() function from exiting the whole process. exit() will always be allowed in the interactive console and it will always have effect. The only difference is to what level.

I would be happy to rename it to something better, but allow_exit probably does not describe the behavior well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this name is tricky. Maybe one of the other reviewers has an idea? If not, we can go with block_exit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Maybe ignore_exit=False?

When I see block here, it makes me think of a syntactic block (noun), or blocking (verb, as in "a blocking call"). It doesn't suggest "catch SystemExit" to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it's not "ignoring" exit(), like I said above, exit() has effect, just different when the option is toggled. How about something like local_exit? Meaning we will only "exit" the interpreter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Maybe ignore_exit=False?

When I see block here, it makes me think of a syntactic block (noun), or blocking (verb, as in "a blocking call"). It doesn't suggest "catch SystemExit" to me.

Circling back to this as I still think this PR has some value. Let's figure out a name for it. Do you like use_local_exit or local_exit? Or maybe a vague description like safe_exit? Or we can label its actual mechanism - exit_without_closing_stdin - that's the key thing we need - the existing behavior also raises SystemExit but stdin is closed so we are basically done.

@brandtbucher brandtbucher self-requested a review April 25, 2023 17:51
Comment on lines +227 to +228
# process. exit and quit in builtins closes sys.stdin which makes
# it super difficult to restore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting! I didn't know about this additional wrinkle... thanks for the comment.

Lib/code.py Outdated
Comment on lines 321 to 322
self.eof = 'Ctrl-Z plus Return' if sys.platform == "win32" else \
'Ctrl-D (i.e. EOF)'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I personally prefer an if-else to a ternary with a backslash. Also, site.py uses os.pathsep for this purpose... so let's maybe just be consistent with that?

Suggested change
self.eof = 'Ctrl-Z plus Return' if sys.platform == "win32" else \
'Ctrl-D (i.e. EOF)'
if os.sep == '\\':
self.eof = "Ctrl-Z plus Return"
else:
self.eof = "Ctrl-D (i.e. EOF)"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with the if/else block instead of an inline statement, but I kind of changed the OS check on purpose.

The fundamental thing we want to know here is what's the EOF on a specific system, and the best effort we can make is to get the OS type - if it's Windows then it's Ctrl+Z + Return. os.sep is a design decision(finger print?) for Windows, the logic would be: os.sep == '\\' -> "this is Windows!". The seperator itself has nothing to do with what the eof directly, it just happens to be linked together because of Windows.

For similar checks, there are a lot of usage of sys.platform == "win32" in the standard library, and almost only site.py uses os.sep to figure out the OS platform. So I think sys.platform is actually preferred in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the site.py code be a holdover from DOS, which isn’t Windows but still uses \ ? Presumably other antique OS’es using \ also used ^Z ? Not that it matters today. :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad I'm not young enough to ask the question "what is DOS". That's a possibility, but the connection between \\ and Ctrl+Z is still indirect. I don't know if we can build modern Python on ancient OS anymore, always assume that Windows is the only outlier.

@@ -276,8 +315,20 @@ def raw_input(self, prompt=""):
return input(prompt)


class Quitter:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just add a short docstring explaining that this is based on _sitebuiltins.Quitter, and how it is different.

Perhaps it's even worth just subclassing Quitter and overriding __call__? Not sure. These are both pretty simple, but also pretty tightly coupled. Your call.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subclassing Quitter won't give a lot of benefit, because the OS checking is outside the original Quitter class. We could calculate eof outside of the __init__ function, it just seems like unnecessary. The class itself is already very compact and easy to understand.

@@ -23,27 +23,33 @@ build applications which provide an interactive interpreter prompt.
``'__doc__'`` set to ``None``.


.. class:: InteractiveConsole(locals=None, filename="<console>")
.. class:: InteractiveConsole(locals=None, filename="<console>", block_exit=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Maybe ignore_exit=False?

When I see block here, it makes me think of a syntactic block (noun), or blocking (verb, as in "a blocking call"). It doesn't suggest "catch SystemExit" to me.

Comment on lines 37 to 46
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local* is
provided, it is passed to the :class:`InteractiveConsole` constructor for
use as the default namespace for the interpreter loop. The :meth:`interact`
method of the instance is then run with *banner* and *exitmsg* passed as the
banner and exit message to use, if provided. The console object is discarded
after use.
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local*
or *block_exit* is provided, it is passed to the :class:`InteractiveConsole`
constructor for use as the default namespace for the interpreter loop.
The :meth:`interact` method of the instance is then run with *banner* and
*exitmsg* passed as the banner and exit message to use, if provided.
The console object is discarded after use.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please! :)

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like several review comments didn't see action yet?

Regarding the name for the flag, was it already decided that we can't just always overwrite those two builtins? (Why are they closing stdin anyways? I don't recall; maybe you can find a clue in the code? It may be too long ago for the commit history to be useful.)

If we do need this feature, since the name has proved tricky, let's go with local_exit. The name probably doesn't matter too much since the feature feels pretty obscure.

Comment on lines 37 to 46
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local* is
provided, it is passed to the :class:`InteractiveConsole` constructor for
use as the default namespace for the interpreter loop. The :meth:`interact`
method of the instance is then run with *banner* and *exitmsg* passed as the
banner and exit message to use, if provided. The console object is discarded
after use.
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local*
or *block_exit* is provided, it is passed to the :class:`InteractiveConsole`
constructor for use as the default namespace for the interpreter loop.
The :meth:`interact` method of the instance is then run with *banner* and
*exitmsg* passed as the banner and exit message to use, if provided.
The console object is discarded after use.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be done.

Doc/library/code.rst Outdated Show resolved Hide resolved
@gaogaotiantian gaogaotiantian changed the title gh-102895 Add an option block_exit in code.interact to block exit() from terminating the whole process gh-102895 Add an option local_exit in code.interact to block exit() from terminating the whole process Sep 12, 2023
@gaogaotiantian
Copy link
Member Author

Looks like several review comments didn't see action yet?

Regarding the name for the flag, was it already decided that we can't just always overwrite those two builtins? (Why are they closing stdin anyways? I don't recall; maybe you can find a clue in the code? It may be too long ago for the commit history to be useful.)

If we do need this feature, since the name has proved tricky, let's go with local_exit. The name probably doesn't matter too much since the feature feels pretty obscure.

Yes, I waited for the name confirmation as almost all files require that name. I updated all the files to use the name local_exit.

By overwriting those two builtins, what do you mean? Change the builtin classes? They close stdin because

Shells like IDLE catch the SystemExit, but listen when their stdin wrapper is closed.

@gvanrossum
Copy link
Member

@brandtbucher: Could you take over review here? I don't want this to languish for another 5 months (but neither is it urgent).

@brandtbucher
Copy link
Member

Yep, I can.

@brandtbucher brandtbucher self-assigned this Sep 12, 2023
Copy link
Contributor

@MaxwellDupre MaxwellDupre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes LGTM but I see Merging is blocked, hence suggest this is moved up to near the top.

@gaogaotiantian
Copy link
Member Author

Taking a note here that this would fix #85268

@brandtbucher
Copy link
Member

Alright, I think this has marinated for long enough. :)

@brandtbucher brandtbucher merged commit e6eb8ca into python:main Oct 18, 2023
23 checks passed
@gaogaotiantian gaogaotiantian deleted the code-interact-exit branch October 18, 2023 18:52
aisk pushed a commit to aisk/cpython that referenced this pull request Feb 11, 2024
Glyphack pushed a commit to Glyphack/cpython that referenced this pull request Sep 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants